package com.gitee.dictionary.register;

import com.gitee.dictionary.base.BaseDictionaryEnum;
import com.gitee.dictionary.base.HandleDictionaryEnums;
import com.gitee.dictionary.config.DictionaryEnumAutoConfig;
import com.gitee.dictionary.handle.DictionaryEnumHandler;
import com.gitee.dictionary.handle.DictionaryEnumsHandler;
import com.gitee.dictionary.properties.DictionaryEnumProperties;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.lang.NonNull;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * @author <a href="mailto:lin-mt@outlook.com">lin-mt</a>
 */
public class DictionaryEnumTypeHandlerRegister<T extends BaseDictionaryEnum>
        implements ApplicationRunner, ApplicationContextAware {
    
    private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
    
    private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();
    
    private final Logger logger = LoggerFactory.getLogger(DictionaryEnumAutoConfig.class);
    
    private final DictionaryEnumProperties dictionaryEnumProperties;
    
    private final HandleDictionaryEnums<T> handleDictionaryEnums;
    
    private ApplicationContext applicationContext;
    
    public DictionaryEnumTypeHandlerRegister(@NonNull DictionaryEnumProperties dictionaryEnumProperties,
            HandleDictionaryEnums<T> handleDictionaryEnums) {
        this.dictionaryEnumProperties = dictionaryEnumProperties;
        this.handleDictionaryEnums = handleDictionaryEnums;
    }
    
    private Set<Class<T>> scanClasses(String packagePatterns, @NonNull Class<T> clazz) throws IOException {
        Set<Class<T>> classes = new HashSet<>();
        String[] packagePatternArray = StringUtils
                .tokenizeToStringArray(packagePatterns, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packagePattern : packagePatternArray) {
            Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(
                    ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils
                            .convertClassNameToResourcePath(packagePattern) + "/**/*.class");
            for (Resource resource : resources) {
                try {
                    ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource)
                            .getClassMetadata();
                    Class<?> scanClazz = Resources.classForName(classMetadata.getClassName());
                    if (clazz.isAssignableFrom(scanClazz)) {
                        //noinspection unchecked
                        classes.add((Class<T>) scanClazz);
                    }
                } catch (Throwable e) {
                    this.logger.warn("Cannot load the '{}'. Cause by {}", resource, e.toString());
                }
            }
        }
        return classes;
    }
    
    @Override
    public void run(ApplicationArguments args) throws IOException {
        String scanPackages = this.dictionaryEnumProperties.getEnumPackages();
        Class<?> baseClass = this.dictionaryEnumProperties.getBaseEnumClass();
        // 扫描指定包下的数据字典枚举
        if (Strings.isBlank(scanPackages)) {
            Class<?> mainClass = null;
            try {
                StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
                for (StackTraceElement stackTraceElement : stackTrace) {
                    if ("main".equals(stackTraceElement.getMethodName())) {
                        mainClass = Class.forName(stackTraceElement.getClassName());
                    }
                }
            } catch (ClassNotFoundException e) {
                this.logger.error("扫描数据字典枚举信息，获取启动类出错", e);
            }
            if (null != mainClass) {
                // 扫描工作目录下的所有实现 baseClass 的数据字典枚举
                scanPackages = ClassUtils.getPackageName(mainClass);
            }
        }
        if (null == baseClass) {
            baseClass = BaseDictionaryEnum.class;
        }
        //noinspection unchecked
        Set<Class<T>> classes = this.scanClasses(scanPackages, (Class<T>) baseClass);
        if (this.dictionaryEnumProperties.getEnableCodeCheck()) {
            // 检查Code
            this.handleDictionaryEnums.handle(classes);
        }
        if (!CollectionUtils.isEmpty(classes)) {
            SqlSessionFactory sqlSessionFactory = this.applicationContext.getBean(SqlSessionFactory.class);
            TypeHandlerRegistry typeHandlerRegistry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry();
            Set<Class<T>> dictionaryEnumInterfaces = new HashSet<>();
            Iterator<Class<T>> iterator = classes.iterator();
            while (iterator.hasNext()) {
                Class<T> next = iterator.next();
                if (next.isEnum()) {
                    typeHandlerRegistry.register(next, new DictionaryEnumHandler<>(next));
                    continue;
                }
                if (next.isInterface()) {
                    dictionaryEnumInterfaces.add(next);
                }
                // 去掉非枚举的类和接口
                iterator.remove();
            }
            
            if (!CollectionUtils.isEmpty(dictionaryEnumInterfaces)) {
                Set<TypeHandler<T>> typeHandlers = new HashSet<>();
                Set<Class<T>> subclasses = new HashSet<>();
                for (Class<T> dictionaryEnumInterface : dictionaryEnumInterfaces) {
                    for (Class<T> dictionaryEnum : classes) {
                        if (dictionaryEnumInterface.isAssignableFrom(dictionaryEnum)) {
                            subclasses.add(dictionaryEnum);
                        }
                    }
                    if (!CollectionUtils.isEmpty(subclasses)) {
                        for (Class<T> subclass : subclasses) {
                            typeHandlers.add(typeHandlerRegistry.getTypeHandler(subclass));
                        }
                        typeHandlerRegistry
                                .register(dictionaryEnumInterface, new DictionaryEnumsHandler<>(typeHandlers));
                    }
                }
            }
        }
    }
    
    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
